diff options
| author | joonhoekim <26rote@gmail.com> | 2025-10-27 17:14:44 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-10-27 17:14:44 +0900 |
| commit | 02062af723f1a3c2994c3e80148da47b07712713 (patch) | |
| tree | fa9e27d9b0d9a9f87ef7ccf05edfb9c2806c06bd /app/api/swp/download/[fileId]/route.ts | |
| parent | 2e92d5f83ae5f0f39090552b46c519982e9279c9 (diff) | |
(김준회) SWP 다운로드, 업로드 api route 처리, 옥프로 컬럼 순서 조정 처리, 환경변수 오타 수정
Diffstat (limited to 'app/api/swp/download/[fileId]/route.ts')
| -rw-r--r-- | app/api/swp/download/[fileId]/route.ts | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/app/api/swp/download/[fileId]/route.ts b/app/api/swp/download/[fileId]/route.ts new file mode 100644 index 00000000..3af560aa --- /dev/null +++ b/app/api/swp/download/[fileId]/route.ts @@ -0,0 +1,172 @@ +import { NextRequest, NextResponse } from "next/server"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { eq } from "drizzle-orm"; +import db from "@/db/db"; +import { swpDocumentFiles } from "@/db/schema/SWP/swp-documents"; +import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils"; + +// API Route 설정 +export const runtime = "nodejs"; +export const maxDuration = 60; // 1분 타임아웃 + +/** + * GET /api/swp/download/[fileId] + * 파일 다운로드 (바이너리 직접 전송) + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ fileId: string }> } +) { + try { + const { fileId: fileIdString } = await params; + const fileId = parseInt(fileIdString, 10); + + if (isNaN(fileId)) { + return NextResponse.json( + { success: false, error: "잘못된 파일 ID입니다." }, + { status: 400 } + ); + } + + debugLog(`[download] 다운로드 시작`, { fileId }); + + // 1. 파일 정보 조회 + const fileInfo = await db + .select({ + FILE_NM: swpDocumentFiles.FILE_NM, + FLD_PATH: swpDocumentFiles.FLD_PATH, + }) + .from(swpDocumentFiles) + .where(eq(swpDocumentFiles.id, fileId)) + .limit(1); + + if (!fileInfo || fileInfo.length === 0) { + debugError(`[download] 파일 정보 없음`, { fileId }); + return NextResponse.json( + { success: false, error: "파일 정보를 찾을 수 없습니다." }, + { status: 404 } + ); + } + + const { FILE_NM, FLD_PATH } = fileInfo[0]; + debugLog(`[download] 파일 정보 조회 완료`, { FILE_NM, FLD_PATH }); + + if (!FLD_PATH || !FILE_NM) { + debugError(`[download] 파일 경로 또는 이름 없음`, { FILE_NM, FLD_PATH }); + return NextResponse.json( + { success: false, error: "파일 경로 또는 파일명이 없습니다." }, + { status: 404 } + ); + } + + // 2. NFS 마운트 경로 확인 + const nfsBasePath = process.env.SWP_MOUNT_DIR; + if (!nfsBasePath) { + debugError(`[download] SWP_MOUNT_DIR 환경변수가 설정되지 않았습니다.`); + return NextResponse.json( + { success: false, error: "서버 설정 오류: NFS 경로가 설정되지 않았습니다." }, + { status: 500 } + ); + } + + // 3. 전체 파일 경로 생성 + const normalizedFldPath = FLD_PATH.replace(/\\/g, '/'); + const fullPath = path.join(nfsBasePath, normalizedFldPath, FILE_NM); + + debugLog(`[download] 파일 경로`, { + fileId, + FILE_NM, + FLD_PATH, + normalizedFldPath, + fullPath, + }); + + // 4. 파일 존재 여부 확인 + try { + await fs.access(fullPath, fs.constants.R_OK); + } catch (accessError) { + debugError(`[download] 파일 접근 불가`, { fullPath, error: accessError }); + return NextResponse.json( + { success: false, error: `파일을 찾을 수 없습니다: ${FILE_NM}` }, + { status: 404 } + ); + } + + // 5. 파일 읽기 + debugLog(`[download] 파일 읽기 시작`, { fullPath }); + const fileBuffer = await fs.readFile(fullPath); + + debugLog(`[download] 파일 Buffer 읽기 완료`, { + bufferLength: fileBuffer.length, + isBuffer: Buffer.isBuffer(fileBuffer), + bufferType: typeof fileBuffer, + constructor: fileBuffer.constructor.name, + first20Bytes: fileBuffer.slice(0, 20).toString('hex') + }); + + // 6. MIME 타입 결정 + const mimeType = getMimeType(FILE_NM); + + debugSuccess(`[download] 다운로드 성공`, { + fileName: FILE_NM, + size: fileBuffer.length, + mimeType, + }); + + // 7. 바이너리 응답 반환 + return new NextResponse(fileBuffer, { + status: 200, + headers: { + "Content-Type": mimeType, + "Content-Disposition": `attachment; filename="${encodeURIComponent(FILE_NM)}"`, + "Content-Length": String(fileBuffer.length), + }, + }); + } catch (error) { + console.error("[download] 오류:", error); + debugError(`[download] 다운로드 실패`, { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "파일 다운로드 실패", + }, + { status: 500 } + ); + } +} + +/** + * MIME 타입 결정 + */ +function getMimeType(fileName: string): string { + const ext = path.extname(fileName).toLowerCase(); + + const mimeTypes: Record<string, string> = { + ".pdf": "application/pdf", + ".doc": "application/msword", + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".xls": "application/vnd.ms-excel", + ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".ppt": "application/vnd.ms-powerpoint", + ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".txt": "text/plain", + ".csv": "text/csv", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".png": "image/png", + ".gif": "image/gif", + ".zip": "application/zip", + ".rar": "application/x-rar-compressed", + ".7z": "application/x-7z-compressed", + ".dwg": "application/acad", + ".dxf": "application/dxf", + }; + + return mimeTypes[ext] || "application/octet-stream"; +} + |
